package top.superflow.db.orientdb

import scala.collection.JavaConverters._
import scala.collection.mutable.Map
import org.apache.commons.lang3.StringUtils
import com.orientechnologies.orient.core.metadata.schema.OClass
import com.orientechnologies.orient.core.metadata.schema.OClass.INDEX_TYPE
import com.orientechnologies.orient.core.metadata.schema.OProperty
import com.orientechnologies.orient.core.metadata.schema.OType
import top.superflow.db.api.DBField
import top.superflow.db.api.DBIndex
import top.superflow.db.api.DBEntity
import top.superflow.exception.RequestUnsupportedException
import top.superflow.log.Loggable
import top.superflow.verify.Ensure

class OrientEntity (realEntity : OClass) extends DBEntity  with Ensure with Loggable {
  
  notNull(realEntity)
  
  val dbEntity = realEntity
  val fields = Map[String, DBField]()
  val indexs = Map[String, DBIndex]()
  val indexsByColumn = Map[String, List[DBIndex]]()
  
  init
  
  private def init = {
    initFields
    intitIndexs
  }
  
  private def initFields = {
    for(originalField <- dbEntity.properties().asScala){
      val field = new DBField

      field.name = originalField.getName
      field.fieldType = convertType(originalField.getType)
      field.defaultValue = originalField.getDefaultValue
      field.nullable = !originalField.isNotNull()

      fields += (field.name -> field)
    }
  }
  
  private def intitIndexs = {
    for(originalField <- dbEntity.properties().asScala){
    
      val realIndexes = this.dbEntity.getIndexes
      
      for(index <- realIndexes.asScala){
        val dbIndex = new OrientIndex(index)
        val fields = index.getDefinition.getFields          
        
        //store index by index name
        indexs += (dbIndex.name -> dbIndex)
        
        //store index by field
        var indexByColumn = indexsByColumn.get(originalField.getName)
        if(indexByColumn.isEmpty){
          indexsByColumn += (originalField.getName -> List[DBIndex]())
          indexByColumn = indexsByColumn.get(originalField.getName)
        }
        indexByColumn.get :+ dbIndex
      }
    }
  }
  
  
  
  
  def existsProperty (propertyName : String): Boolean = {
    this.dbEntity.existsProperty(propertyName)
  }
  
  def addField(field : DBField): Unit= {
    notNull(field.name)
    
    if(this.dbEntity.existsProperty(field.name)){
      logger.warn("The column {} has already existed.", field.name)
      return
    }
    
    val property = createProperty(field)
    createIndex(field, property)
  }

  def getField( name : String) : Option[DBField] = {
    if(!this.dbEntity.existsProperty(name)){
      return None
    }
    
    fields.get(name)
  }
  
  def getIndexWrappers (columnName : String): Option[List[DBIndex]] = {
    notEmpty(columnName, "columnName should not be null")
    
    indexsByColumn.get(columnName)
  }
  
  def getFields: Option[List[DBField]] = {
    Some(fields.valuesIterator.toList)
  }
  
  def getIndexs: Option[List[DBIndex]] = {
    Some(indexs.valuesIterator.toList)
  }
  def getEntityName : String = {
    this.dbEntity.getName
  }
  
  
  private def convertType(oType : OType): String = {
    notNull(oType)
    oType.toString()
  }
  
  
  private def createIndex(field: DBField, property: OProperty): Unit = {
    if(field.indexes == null  || field.indexes.length == 0){
      return;
    }
      
    for(index <- field.indexes){
      if(index.unique){
        property.createIndex(INDEX_TYPE.UNIQUE_HASH_INDEX)
      }else{
        if(!StringUtils.isEmpty(index.name)){
          property.createIndex(index.name)
        }else{
          val defaultIndexName = "IDX_" + property.getOwnerClass.getShortName + "_" + field.name
          if(existIndex(index.name)){
            throw new RequestUnsupportedException("For the default index name " + defaultIndexName + ", it exists already, please name it and retry.")
          }
          property.createIndex(defaultIndexName)
        }
      }
    }
  }
  
  private def existIndex(indexName : String) : Boolean = {
    if(StringUtils.isEmpty(indexName)) return false;
    
    val allIndexes = this.dbEntity.getIndexes
    for(index <- allIndexes.asScala){
      if(StringUtils.equals(index.getName, indexName)) {
        return true
      }
    }
    
    false
  }

  private def createProperty(field: DBField) = {
    val property = this.dbEntity.createProperty(field.name, getOType(field.fieldType))
    if(field.length != 0){
      property.setMax(String.valueOf(field.length))
    }
    property
  }
  
  def createIndex(index : DBIndex) = {
    this.dbEntity.createIndex(index.name, getIndexType(index.unique), index.fieldList: _*)
  }
  
  def indexMaps() : Map[String, DBIndex] = {
    val indexMap = Map[String, DBIndex]()
    val indexes = this.dbEntity.getIndexes
    for(index <- indexes.asScala){
      indexMap += (index.getName -> new OrientIndex(index))
    }
    
    indexMap
  }
  
  private def getIndexType(unique : Boolean) : INDEX_TYPE = {
    if(unique){
      INDEX_TYPE.UNIQUE_HASH_INDEX
    }else{
      INDEX_TYPE.NOTUNIQUE_HASH_INDEX
    }
  }
  
  private def getOType(originalType : String) : OType =  {
    println(originalType)
    originalType match {
      case x if(StringUtils.containsIgnoreCase(x, "string")) => return OType.STRING
      case x if(StringUtils.containsIgnoreCase(x, "int")) => return OType.INTEGER
      case _ => throw new RequestUnsupportedException(originalType)
    }
  }
}